﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media.Imaging;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Office2010.PowerPoint;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;
using OpenPowerPoint.Collection;
using OpenPowerPoint.Exceptions;
using OpenPowerPoint.Slides;
using Slide = OpenPowerPoint.Slides.Slide;
using SlideLayout = OpenPowerPoint.Slides.SlideLayout;
using SlideMaster = OpenPowerPoint.Slides.SlideMaster;

namespace OpenPowerPoint
{
    /// <summary>
    /// A single Presentation
    /// </summary>
    /// <remarks>This class represents a single Presentation Document containing all top-level information needed for the Presentation</remarks>
    public class Presentation : DependencyObject
    {
        /// <summary>
        /// A list of supported Image types
        /// </summary>
        public enum ImageType
        {
            /// <summary>
            /// JPEG-Image (*.jpeg, *.jpg)
            /// </summary>
            Jpeg,

            /// <summary>
            /// TIFF-Image (*.tiff, *.tif)
            /// </summary>
            Tiff,

            /// <summary>
            /// PNG-Image (*.png)
            /// </summary>
            Png,

            /// <summary>
            /// BMP-Image (*.bmp)
            /// </summary>
            Bmp,
        }

        /// <summary>
        /// The type of the presentation Document
        /// </summary>
        /// <remarks>Each presentation has a specific type determined by its extension. Since OpenXML adds the extension itself, we have to determine the type here.</remarks>
        public enum PresentationType
        {
            /// <summary>
            /// A normal presentation (.pptx)
            /// </summary>
            Presentation,

            /// <summary>
            /// A normal slideshow (.ppsx)
            /// </summary>
            Slideshow,

            /// <summary>
            /// A normal template (.potx)
            /// </summary>
            Template,

            /// <summary>
            /// A presentation with macros enabled (.pptm)
            /// </summary>
            MacroPresentation,

            /// <summary>
            /// A slideshow with macros enabled (.ppsm)
            /// </summary>
            MacroSlideshow,

            /// <summary>
            /// A template with macros enabled (.potm)
            /// </summary>
            MacroTemplate,

            /// <summary>
            /// A normal AddIn (.ppam)
            /// </summary>
            AddIn,
        }

        #region Load/Save

        /// <summary>
        /// Loads a presentation out of a file
        /// </summary>
        /// <param name="filename">The file to load the presentation from</param>
        /// <returns>A Presentation Object according to the Presentation</returns>
        public static Presentation LoadPresentation(string filename)
        {
            var ret = new Presentation();

            //Open the presentation file
            using (var doc = PresentationDocument.Open(filename, false))
            {
                LoadContent(doc, ref ret);
            }

            ret.Filename = filename;

            return ret;
        }

        /// <summary>
        /// Loads a presentation out of a stream
        /// </summary>
        /// <param name="file">The stream to open the presentation from</param>
        /// <returns>A Presentation Object according to the stream</returns>
        /// <remarks>This function accepts a stream and creates a presentation object out of the stream. Since this function is based on a stream, the filename will remain empty</remarks>
        public static Presentation LoadPresentation(Stream file)
        {
            var ret = new Presentation();

            using (var doc = PresentationDocument.Open(file, false))
            {
                LoadContent(doc, ref ret);
                ret.FileProperties = Properties.Load(doc.CoreFilePropertiesPart,
                                                     doc.ExtendedFilePropertiesPart.Properties);
            }

            return ret;
        }

        /// <summary>
        /// Saves the presentation
        /// </summary>
        /// <remarks>This function saves the presentation to the file it was loaded from. If there was no file, an exception will be thrown.</remarks>
        public void Save()
        {
            if (Filename != "" && Uri.IsWellFormedUriString(Filename, UriKind.Absolute))
                Save(Filename);

            //We cannot save to file without a filename
            throw new IOException("No filename specified");
        }

        /// <summary>
        /// Saves the presentation
        /// </summary>
        /// <param name="filename">The file to save the presentation to</param>
        public void Save(string filename)
        {
            //Open a new relation stack for the root document
            RelationResolver.StartRelationStack();

            using (var doc = PresentationDocument.Create(Filename, (PresentationDocumentType)Type))
            {
                var presentation = doc.AddPresentationPart();
                var extensions = new PresentationExtensionList();
                var groupExtension = new PresentationExtension { Uri = NamespaceUris.pres_groups };
                var groupList = new SectionList();

                //Add Namespaces
                groupList.AddNamespaceDeclaration("p14", "http://schemas.microsoft.com/office/powerpoint/2010/main");
                presentation.Presentation.AddNamespaceDeclaration("a", "http://schemas.openxmlformats.org/drawingml/2006/main");
                presentation.Presentation.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
                presentation.Presentation.AddNamespaceDeclaration("p", "http://schemas.openxmlformats.org/presentationml/2006/main");

                #region Thumbnail

                var thumbnail = doc.AddNewPart<ThumbnailPart>("image/" + ThumbnailType.ToString().ToLower(),
                                                                        RelationResolver.GetNextRelationId());

                Stream stream = new MemoryStream();

                BitmapEncoder encoder;

                switch (ThumbnailType)
                {
                    case ImageType.Tiff:
                        encoder = new TiffBitmapEncoder();
                        break;

                    case ImageType.Png:
                        encoder = new PngBitmapEncoder();
                        break;

                    case ImageType.Bmp:
                        encoder = new BmpBitmapEncoder();
                        break;

                    default:
                        encoder = new JpegBitmapEncoder();
                        break;
                }

                encoder.Frames.Add(BitmapFrame.Create(Thumbnail));
                encoder.Save(stream);

                stream.Seek(0, SeekOrigin.Begin);
                thumbnail.FeedData(stream);

                #endregion Thumbnail

                //Open a new relation stack for the presentation part
                RelationResolver.StartRelationStack();

                //Iterate trough every child
                foreach (var master in SlideMasters)
                {
                    master.Save(ref presentation);
                }

                foreach (var groups in Groups)
                {
                    groups.Save(ref presentation, ref groupList);
                }

                //Add everything to the presentation
                groupExtension.Append(groupList);
                extensions.Append(groupExtension);

                presentation.Presentation.Append(extensions);
            }

            foreach (var master in SlideMasters)
            {
                master.Saved();
            }

            foreach (var group in Groups)
            {
                group.Saved();
            }
        }

        /// <summary>
        /// Loads the content out of a presentation document
        /// </summary>
        /// <param name="doc">The document to read from</param>
        /// <param name="presentation">The presentation to append the information</param>
        /// <remarks>This function gets called by the <see cref="LoadPresentation"/> overloads and fills the final Presentation object with its content.</remarks>
        private static void LoadContent(PresentationDocument doc, ref Presentation presentation)
        {
            var part = doc.PresentationPart;

            if (part != null)
            {
                //Load Presentation Theme
                presentation.Theme = Theme.Load(part.ThemePart.Theme);

                //load slide masters
                int slides = part.SlideMasterParts.Count();

                for (int i = 0; i < slides; i++)
                {
                    presentation.SlideMasters.Add(SlideMaster.Load(part.SlideMasterParts.ElementAt(i)));
                }

                //load slidegroups
                try
                {
                    var groups = part.Presentation.PresentationExtensionList.First(x => ((PresentationExtension)x).Uri == NamespaceUris.pres_groups).GetFirstChild<SectionList>();

                    for (int i = 0; i < groups.Count(); i++)
                    {
                        presentation.Groups.Add(SlideGroup.Load((Section)groups.ElementAt(i), part));
                    }

                    //No groups found, add default group
                    if (!groups.Any())
                    {
                        presentation.Groups.Add(SlideGroup.Load("default", part.SlideParts));
                    }
                }
                catch (ArgumentNullException)//No group extension found, add default group
                {
                    presentation.Groups.Add(SlideGroup.Load("default", part.SlideParts));
                }

                //Remove default groups initialized by constructor
                presentation.Groups.RemoveAt(0);
                presentation.SlideMasters.RemoveAt(0);
            }

            //Load others
            var size = part.Presentation.GetFirstChild<SlideSize>();
            if (size != null)
            {
                presentation.SlideSize = new Vector(Conversion.UnitConversion.EmuToPt(size.Cx), Conversion.UnitConversion.EmuToPt(size.Cy));
            }

            if (doc.GetPartsOfType<ThumbnailPart>().Any())
            {
                #region Thumbnail

                Stream thumbnail = doc.GetPartsOfType<ThumbnailPart>().ElementAt(0).GetStream();
                thumbnail.Seek(0, SeekOrigin.Begin);

                switch (doc.GetPartsOfType<ThumbnailPart>().ElementAt(0).ContentType)
                {
                    case "image/jpeg":
                        presentation.ThumbnailType = ImageType.Jpeg;
                        break;

                    case "image/png":
                        presentation.ThumbnailType = ImageType.Png;
                        break;

                    case "image/tiff":
                        presentation.ThumbnailType = ImageType.Tiff;
                        break;

                    case "image/bmp":
                        presentation.ThumbnailType = ImageType.Bmp;
                        break;
                }
                presentation.Thumbnail = new BitmapImage();
                presentation.Thumbnail.BeginInit();
                presentation.Thumbnail.StreamSource = thumbnail;
                presentation.Thumbnail.CacheOption = BitmapCacheOption.OnLoad;
                presentation.Thumbnail.EndInit();
                thumbnail.Close();

                #endregion Thumbnail
            }

            presentation.Type = (PresentationType)doc.DocumentType;
        }
        #endregion Load/Save

        #region Constructor

        /// <summary>
        /// Initializes a new Instance of <see cref="Presentation"/>
        /// </summary>
        /// <param name="type">The type of the presentation <seealso cref="PresentationType"/></param>
        public Presentation(PresentationType type)
            : this()
        {
            Type = type;
        }

        /// <summary>
        /// Initializes a new Instance of <see cref="Presentation"/>
        /// </summary>
        /// <remarks>
        /// This is an empty constructor for internal access only, because it is guaranted, that the load mechanism will do the rest for us.
        /// </remarks>
        private Presentation()
        {
            Filename = "";
            Theme = Slides.Themes.Larissa;
            Groups = new NotifyableList<SlideGroup>() { new SlideGroup("default", new ObservableCollection<Slide>()) };
            SlideMasters = new NotifyableList<SlideMaster>() { new SlideMaster(new SlideLayout()) };
        }
        #endregion Constructor

        #region Properties

        public static readonly DependencyProperty FilenameProperty =
            DependencyProperty.Register("Filename", typeof(string), typeof(Presentation), new PropertyMetadata(default(string)));

        public static readonly DependencyProperty FilePropertiesProperty =
            DependencyProperty.Register("FileProperties", typeof(Properties), typeof(Presentation), new PropertyMetadata(default(Properties)));

        public static readonly DependencyProperty GroupsProperty =
            DependencyProperty.Register("Groups", typeof(NotifyableList<SlideGroup>), typeof(Presentation),
                                        new PropertyMetadata(default(NotifyableList<SlideGroup>),
                                                             (o, e) =>
                                                             {
                                                                 if (e.NewValue != null)
                                                                     ((NotifyableList<SlideGroup>)e.NewValue).PropertyChanged +=
                                                                         ((Presentation)o).OnGroupPropertyChanged;
                                                                 if (e.OldValue != null)
                                                                     ((NotifyableList<SlideGroup>)e.OldValue).PropertyChanged -=
                                                                         ((Presentation)o).OnGroupPropertyChanged;
                                                             }));

        public static readonly DependencyProperty SlideMastersProperty =
            DependencyProperty.Register("SlideMasters", typeof(NotifyableList<SlideMaster>), typeof(Presentation),
                                        new PropertyMetadata(default(NotifyableList<SlideMaster>), (o, e) =>
                                                                                                       {
                                                                                                           if (e.NewValue != null)
                                                                                                               ((NotifyableList<SlideMaster>)e.NewValue).PropertyChanged +=
                                                                                                                   ((Presentation)o).OnSlideMasterPropertyChanged;
                                                                                                           if (e.OldValue != null)
                                                                                                               ((NotifyableList<SlideMaster>)e.OldValue).PropertyChanged -=
                                                                                                                   ((Presentation)o).OnSlideMasterPropertyChanged;
                                                                                                       }));

        public static readonly DependencyProperty SlideSizeProperty =
            DependencyProperty.Register("SlideSize", typeof(Vector), typeof(Presentation), new PropertyMetadata(new Vector(0, 0)));

        public static readonly DependencyProperty ThemesProperty =
                    DependencyProperty.Register("Theme", typeof(Theme), typeof(Presentation),
                                                new PropertyMetadata(default(Theme), (o, e) =>
                                                                                                        {
                                                                                                            if (e.NewValue != null)
                                                                                                                ((Theme)e.NewValue).PropertyChanged +=
                                                                                                                    ((Presentation)o).OnThemePropertyChanged;
                                                                                                            if (e.OldValue != null)
                                                                                                                ((Theme)e.OldValue).PropertyChanged -=
                                                                                                                    ((Presentation)o).OnThemePropertyChanged;
                                                                                                        }));

        public static readonly DependencyProperty ThumbnailProperty =
                    DependencyProperty.Register("Thumbnail", typeof(BitmapImage), typeof(Presentation), new PropertyMetadata(default(BitmapImage)));

        public static readonly DependencyProperty ThumbnailTypeProperty =
                    DependencyProperty.Register("ThumbnailType", typeof(ImageType), typeof(Presentation), new PropertyMetadata(default(ImageType)));

        public static readonly DependencyProperty TypeProperty =
                    DependencyProperty.Register("PresentationType", typeof(PresentationType), typeof(Presentation), new PropertyMetadata(default(PresentationType)));

        /// <summary>
        /// The filename of the file loaded
        /// </summary>
        public string Filename
        {
            get { return (string)GetValue(FilenameProperty); }
            set { SetValue(FilenameProperty, value); }
        }

        /// <summary>
        /// The properties of this presentation
        /// </summary>
        public Properties FileProperties
        {
            get { return (Properties)GetValue(FilePropertiesProperty); }
            set { SetValue(FilePropertiesProperty, value); }
        }

        /// <summary>
        /// All groups contained in this presentation
        /// </summary>
        /// <remarks>This list also contains the group "default", which is the default group and will not be saved</remarks>
        public ObservableCollection<SlideGroup> Groups
        {
            get { return (ObservableCollection<SlideGroup>)GetValue(GroupsProperty); }
            set { SetValue(GroupsProperty, value); }
        }

        /// <summary>
        /// A list of slide masters used in this presentation
        /// </summary>
        public ObservableCollection<SlideMaster> SlideMasters
        {
            get { return (ObservableCollection<SlideMaster>)GetValue(SlideMastersProperty); }
            set { SetValue(SlideMastersProperty, value); }
        }

        /// <summary>
        /// The size for all slides
        /// </summary>
        public Vector SlideSize
        {
            get { return (Vector)GetValue(SlideSizeProperty); }
            set { SetValue(SlideSizeProperty, value); }
        }
        /// <summary>
        /// The Theme representing this presentation
        /// </summary>
        public Theme Theme
        {
            get { return (Theme)GetValue(ThemesProperty); }
            set { SetValue(ThemesProperty, value); }
        }

        /// <summary>
        /// The thumbnail image provided with this presentation
        /// </summary>
        public BitmapImage Thumbnail
        {
            get { return (BitmapImage)GetValue(ThumbnailProperty); }
            set { SetValue(ThumbnailProperty, value); }
        }

        /// <summary>
        /// The type of the thumbnail image
        /// </summary>
        public ImageType ThumbnailType
        {
            get { return (ImageType)GetValue(ThumbnailTypeProperty); }
            set { SetValue(ThumbnailTypeProperty, value); }
        }

        /// <summary>
        /// Determines the type of the presentation
        /// </summary>
        public PresentationType Type
        {
            get { return (PresentationType)GetValue(TypeProperty); }
            set { SetValue(TypeProperty, value); }
        }
        #endregion Properties

        #region Functions

        #region Slides

        /// <summary>
        /// Adds a new slide at the end of the presentation
        /// </summary>
        /// <returns>An instance of the new slide</returns>
        public Slide AddSlide()
        {
            Slide ret = new Slide();

            Groups[Groups.Count - 1].Slides.Add(ret);

            return ret;
        }

        /// <summary>
        /// Inserts a new slide into the group at this position
        /// </summary>
        /// <param name="index">The index of the new slide</param>
        /// <returns>An instance of the new slide</returns>
        /// <remarks>If the index is beyond the number of slides, a slide will be added at the end of the presentation</remarks>
        /// <exception cref="ArgumentOutOfRangeException">The index is lower than zero</exception>
        public Slide AddSlide(int index)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("index has to be greater or equal zero");

            //Variables
            int i = 0;
            int g_index = -1;
            int temp_index = 0;

            //Check which group contains the index
            for (i = 0; i < Groups.Count; i++)
            {
                if (temp_index + Groups[i].Slides.Count > index)
                {
                    g_index = index - temp_index;
                    break;
                }

                temp_index += Groups[i].Slides.Count;
            }

            Slide slide = new Slide();

            //If no group was found, add it to the end
            if (g_index == -1)
            {
                Groups[Groups.Count - 1].Slides.Add(slide);
            }
            else
            {
                Groups[i].Slides.Insert(g_index, slide);
            }

            return slide;
        }

        /// <summary>
        /// Returns the slide at the desired position
        /// </summary>
        /// <param name="index">The 0-based index of the slide</param>
        /// <returns>The slide at the position or null</returns>
        /// <exception cref="ArgumentOutOfRangeException">The index is not in the range of all slides</exception>
        public Slide GetSlide(int index)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("index has to be greater or equal zero");

            //Variables
            int i = 0;
            int temp_index = 0;

            //Get the group
            for (i = 0; i < Groups.Count; i++)
            {
                if (temp_index + Groups[i].Slides.Count > index)
                {
                    return Groups[i].Slides[index - temp_index];
                }

                temp_index += Groups[i].Slides.Count;
            }

            //throw (no slide found)
            throw new ArgumentOutOfRangeException("index was greater than total amount of slides");
        }

        /// <summary>
        /// Removes a slide out of the groups
        /// </summary>
        /// <param name="index">The index of the slide</param>
        /// <returns>true on success</returns>
        /// <exception cref="ArgumentOutOfRangeException">The index is lower than zero</exception>
        public void RemoveSlide(int index)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("index has to be greater or equal zero");

            //Variables
            int i = 0;
            int temp_index = 0;

            //Get the group
            for (i = 0; i < Groups.Count; i++)
            {
                if (temp_index + Groups[i].Slides.Count > index)
                {
                    Groups[i].Slides.RemoveAt(index - temp_index);
                    return;
                }

                temp_index += Groups[i].Slides.Count;
            }

            throw new ArgumentOutOfRangeException("The Index is not part of this presentation");
        }

        /// <summary>
        /// Removes a slide out of the groups
        /// </summary>
        /// <param name="slide">The slide to remove</param>
        /// <exception cref="ArgumentException">The slide was not part of this presentation</exception>
        public void RemoveSlide(Slide slide)
        {
            foreach (var group in Groups)
            {
                if (group.Slides.Contains(slide))
                {
                    group.Slides.Remove(slide);
                    return;
                }
            }

            throw new ArgumentException("This slide is not part of any group");
        }

        /// <summary>
        /// Returns the total number of Slides
        /// </summary>
        /// <returns></returns>
        public int SlideCount()
        {
            return Groups.Sum(x => x.Slides.Count);
        }

        #endregion Slides

        #region Groups

        /// <summary>
        /// Adds a new group to the end of the list
        /// </summary>
        /// <param name="name">The name of the group</param>
        /// <returns>The group instance which was created</returns>
        public SlideGroup AddGroup(string name)
        {
            SlideGroup ret = new SlideGroup(name, new List<Slide>());

            Groups.Add(ret);

            return ret;
        }

        /// <summary>
        /// Adds a new group to the list of groups
        /// </summary>
        /// <param name="name">The name of the group</param>
        /// <param name="index">The index of the first slide belonging to the group</param>
        /// <returns>The group instance which was created</returns>
        /// <exception cref="ArgumentOutOfRangeException">The index is lower than one</exception>
        public SlideGroup AddGroup(string name, int index)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("index has to be greater than zero");

            SlideGroup ret = new SlideGroup(name, new List<Slide>());

            Groups.Insert(index, ret);

            return ret;
        }

        /// <summary>
        /// Gets the slide group at the desired position
        /// </summary>
        /// <param name="index">0-based index of the group</param>
        /// <returns>The slide group at the position</returns>
        /// <exception cref="ArgumentOutOfRangeException">The index was not in the Group List</exception>
        public SlideGroup GetGroup(int index)
        {
            if (index < Groups.Count && index >= 0)
                return Groups[index];

            throw new ArgumentOutOfRangeException("The index is not contained in the group list");
        }

        /// <summary>
        /// Gets the first slide group with the desired name
        /// </summary>
        /// <param name="name">The name of the group to search</param>
        /// <returns>The group with the name</returns>
        /// <exception cref="ArgumentOutOfRangeException">The group "name" was not in the group list</exception>
        public SlideGroup GetGroup(string name)
        {
            try
            {
                return Groups.First(x => x.Name == name);
            }
            catch (InvalidOperationException)
            {
                throw new ArgumentOutOfRangeException("The group is not in the group list");
            }
        }

        /// <summary>
        /// Gets the index of a group based on a name
        /// </summary>
        /// <param name="name">The name of the group</param>
        /// <returns>-1 if not found</returns>
        public int GetGroupIndex(string name)
        {
            for (int i = 0; i < Groups.Count; i++)
            {
                if (Groups[i].Name == name)
                    return i;
            }

            return -1;
        }

        /// <summary>
        /// Returns the
        /// </summary>
        /// <returns></returns>
        public int GroupCount()
        {
            return Groups.Count;
        }

        /// <summary>
        /// Removes a group
        /// </summary>
        /// <param name="index">The 0-based index of the group to remove</param>
        /// <param name="keepSlides">If true, all slides will be added to the group before. The first group will throw an exception</param>
        /// <exception cref="ArgumentException">When keeping slides, there's no group before to take them</exception>
        /// <exception cref="IndexOutOfRangeException">The Index was out of range</exception>
        public void RemoveGroup(int index, bool keepSlides = true)
        {
            if (index < Groups.Count && index >= 0)
            {
                if (keepSlides == false)
                {
                    Groups.RemoveAt(index);
                }
                else
                {
                    if (index > 0)
                    {
                        Collection<Slide> slides = new Collection<Slide>();

                        slides = Groups[index].Slides;

                        //Add slides to group before
                        foreach (var slide in slides)
                        {
                            Groups[index - 1].Slides.Add(slide);
                        }

                        //Unregister slides of dead object
                        Groups[index].Slides.Clear();
                        Groups.RemoveAt(index);
                    }
                    else
                        throw new ArgumentException("Cannot IsRemove first group when keepSlides is true");
                }
            }
            else
            {
                throw new ArgumentOutOfRangeException("Index out of range");
            }
        }

        /// <summary>
        /// Removes a group based on the group name
        /// </summary>
        /// <param name="name">The name to remove</param>
        /// <param name="keepSlides">If true, all slides will be added to the group before. The first group will throw an exception</param>
        public void RemoveGroup(string name, bool keepSlides = true)
        {
            RemoveGroup(GetGroupIndex(name), keepSlides);
        }
        #endregion Groups

        #region Slide Masters

        /// <summary>
        /// Adds a new SlideMaster to the collection
        /// </summary>
        /// <returns></returns>
        public SlideMaster AddSlideMaster()
        {
            SlideMaster master = new SlideMaster(new SlideLayout());

            SlideMasters.Add(master);

            return master;
        }

        /// <summary>
        /// Returns the slideMaster at a specific position
        /// </summary>
        /// <param name="index">The index to get the master from</param>
        /// <returns></returns>
        public SlideMaster GetSlideMaster(int index)
        {
            if (index < 0 || index >= SlideMasters.Count)
                throw new ArgumentOutOfRangeException("Index was out of range");

            return SlideMasters[index];
        }

        /// <summary>
        /// Removes a SlideMaster from the presentation
        /// </summary>
        /// <param name="index">The index of the Master to remove</param>
        public void RemoveSlideMaster(int index)
        {
            if (index < 0 || index >= SlideMasters.Count)
                throw new ArgumentOutOfRangeException("Index was out of range");

            SlideMasters.RemoveAt(index);
        }

        /// <summary>
        /// Returns the number of SlideMasters
        /// </summary>
        /// <returns></returns>
        public int SlideMasterCount()
        {
            return SlideMasters.Count();
        }

        #endregion Slide Masters

        #endregion Functions

        #region INotify

        /// <summary>
        /// Handles changed properties in the group lists
        /// </summary>
        /// <param name="sender">The source of the event</param>
        /// <param name="property">Information about the property and its value</param>
        /// <exception cref="ListNotClearable">Gets thrown when trying to remove too much items, so that no item is left.</exception>
        /// <remarks>This Handler does a little bit more than only handing the event to the next handler. It also handles remove and clear calls on the GroupList. To avoid zero elements in this list,
        /// an exception gets thrown if so. Except for clear - clear removes all items except the first one.</remarks>
        private void OnGroupPropertyChanged(object sender, SProperty property)
        {
            if (sender == Groups)
            {
                if (property.IsClear())
                {
                    //remove all except the first one
                    property.NewValue = "";

                    ((NotifyableList<SlideGroup>)Groups).RemoveRange(1, Groups.Count - 1);
                }

                if (property.IsRemove() && Groups.Count == 1)
                {
                    //Zero items are not allowed, avoid!

                    property.NewValue = "";

                    throw new ListNotClearable("Groups cannot be cleared. There has to be at least one master.");
                }
            }

            //Avoid setting of non-existent slide masters and layouts - simply replace them by the old one.
            else if (sender is Slide)
            {
                if (property.Property == Slide.SlideMasterProperty)
                {
                    if (!SlideMasters.Contains(property.NewValue))
                        property.NewValue = property.OldValue;
                }
                else if (property.Property == Slide.SlideLayoutProperty)
                {
                    if (!((Slide)sender).SlideMaster.Layouts.Contains(property.NewValue))
                        property.NewValue = property.OldValue;
                }
            }
        }

        /// <summary>
        /// Handles changed properties in the slide master lists
        /// </summary>
        /// <param name="sender">The source of the event</param>
        /// <param name="property">Information about the property and its value</param>
        /// <exception cref="ListNotClearable">Gets thrown when trying to remove too much items, so that no item is left.</exception>
        /// <remarks>This Handler does a little bit more than only handing the event to the next handler. It also handles remove and clear calls on the MasterList. To avoid zero elements in this list,
        /// an exception gets thrown if so. Except for clear - clear removes all items except the first one.</remarks>
        private void OnSlideMasterPropertyChanged(object sender, SProperty property)
        {
            if (sender == SlideMasters)
            {
                if (property.IsClear())
                {
                    //remove all except the first one
                    property.NewValue = "";

                    ((NotifyableList<SlideMaster>)SlideMasters).RemoveRange(1, SlideMasters.Count - 1);
                }
                else if (property.IsRemove())
                {
                    List<SlideMaster> removed = new List<SlideMaster>();

                    removed.Add((SlideMaster)property.OldValue);

                    if (SlideMasters.Count == 1)
                    {
                        //Zero items are not allowed, avoid!

                        property.NewValue = "";

                        throw new ListNotClearable("Masters cannot be cleared. There has to be at least one master.");
                    }

                    //Iterate trough each slide and check, whether one of the deleted slide masters was used.
                    //If so, refer to the first slide master.
                    foreach (var group in Groups)
                    {
                        foreach (var slide in group.Slides)
                        {
                            if (removed.Contains(slide.SlideMaster))
                            {
                                //TODO: Fix Bug on removing first entry! List will update after event, not before!
                                slide.SlideMaster = SlideMasters[0];
                                //TODO: Improve: check for same type of layout
                                slide.SlideLayout = slide.SlideMaster.Layouts[0];
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Gets invoked if a theme of the global theme list changes it's value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="property"></param>
        private void OnThemePropertyChanged(object sender, SProperty property)
        {
            /*
            if (sender == Themes)
            {
                if (property.Property == "clear") //Cannot remove all items, instead remove as many as possible
                {
                    property.NewValue = " ";

                    ((NotifyableList<Theme>)Themes).RemoveRange(1, Themes.Count - 1);
                }
                else if (property.Property == "remove")
                {
                    if (Themes.Count == 1)
                        throw new ListNotClearable("Theme list cannot be empty");

                    Theme old = (Theme)property.OldValue;

                    foreach (var master in SlideMasters)
                    {
                        if (master.Theme == old)
                            if (old == Themes[0])
                                master.Theme = Themes[1];
                            else
                                master.Theme = Themes[0];
                    }
                }
            }
            */
        }

        #endregion INotify
    }
}